/*-------------------------------------------------------------------------
 *
 * auth-scram.c
 *	  SASL SCRAM-SHA-256机制的服务器端实现。
 *
 * 有关更多细节，请参见以下RFC：
 * - RFC 5802: https://tools.ietf.org/html/rfc5802
 * - RFC 5803: https://tools.ietf.org/html/rfc5803
 * - RFC 7677: https://tools.ietf.org/html/rfc7677
 *
 * 这里有一些区别：
 *
 * - 身份验证交换中的用户名未使用。客户端应发送一个空字符串作为
 *   用户名。
 *
 * - 如果密码不是有效的UTF-8，或包含SASLprep配置文件禁止的字符，
 *   我们会跳过SASLprep预处理，并在计算哈希时使用原始字节。
 *
 * - 如果使用通道绑定，通道绑定类型始终为“tls-server-end-point”。
 *   规范表示默认值为“tls-unique”
 *   (RFC 5802，第6.1节。默认通道绑定)，但存在一些问题。首先，并非所有
 *   SSL库都提供获取TLS Finished消息的API，使用“tls-unique”是必须的。
 *   其次，“tls-unique”未为TLS v1.3规范化，截至本文撰写时，尚不清楚是否
 *   会有替代品。我们可以同时支持“tls-server-end-point”和“tls-unique”，
 *   但对于我们的用例，“tls-unique”实际上没有任何优势。“tls-unique”的
 *   主要优势是即使服务器没有证书，它也能工作，但无论如何，PostgreSQL要求
 *   每当使用SSL时都要有服务器证书。
 *
 * 存储在pg_authid中的密码包括迭代次数、盐、StoredKey和ServerKey。
 *
 * SASLprep的使用
 * --------------
 *
 * 与SCRAM规范的一个显著区别是，尽管规范规定密码为UTF-8，并禁止某些字符，
 * 我们更为宽松。如果密码不是有效的UTF-8字符串，或包含禁止字符，
 * 则将使用原始字节计算哈希，而不经过SASLprep处理。这是因为PostgreSQL
 * 还支持其他编码，并且在身份验证过程中使用的编码未定义（client_encoding
 * 在身份验证后才设置）。实际上，我们尝试将密码解释为UTF-8并应用
 * SASLprep处理，但如果看起来无效，我们假设它是某种其他编码。
 *
 * 在最坏的情况下，我们将错误解释为不同编码的密码为Unicode，因为它恰
 * 好完全由有效的UTF-8字节构成，并对其应用Unicode规范化。只要我们
 * 始终如一地执行这一步，就不会导致登录失败。幸运的是，SASLprep忽略的
 * UTF-8字节序列与其他受支持编码中通用字符无关，因此即使对非UTF-8
 * 密码错误地应用规范化，也不应导致熵的大量损失。
 *
 * 错误处理
 * --------------
 *
 * 不要向未经过身份验证的客户端透露用户信息。我们不希望攻击者能够探
 * 测特定用户名是否有效。在SCRAM中，服务器必须从用户的存储秘密中读取
 * 盐和迭代次数，并将其发送到客户端。为了避免泄露用户是否存在，当客户端尝试
 * 使用不存在或在pg_authid中没有有效SCRAM秘密的用户名进行身份验证时，
 * 我们动态生成假盐和迭代次数，并使用该信息进行身份验证。最后，我们
 * 将拒绝尝试，就好像提供了错误的密码。当我们执行“伪”身份验证时，
 * scram_state中的‘doomed’标志将被设置。
 *
 * 在错误消息中，避免打印来自客户端的字符串，除非您检查它们是否是
 * 纯ASCII。我们不希望未经过身份验证的攻击者能够用无效于所使用编码的
 * 字符悄悄地填充日志。通常，在登录后我们无法避免这一点，但我们会尽力
 * 在这里做到这一点。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * src/backend/libpq/auth-scram.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <unistd.h>

#include "access/xlog.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_control.h"
#include "common/base64.h"
#include "common/hmac.h"
#include "common/saslprep.h"
#include "common/scram-common.h"
#include "common/sha2.h"
#include "libpq/auth.h"
#include "libpq/crypt.h"
#include "libpq/sasl.h"
#include "libpq/scram.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/timestamp.h"

static void fc_scram_get_mechanisms(Port *fc_port, StringInfo fc_buf);
static void *fc_scram_init(Port *fc_port, const char *fc_selected_mech,
						const char *fc_shadow_pass);
static int	fc_scram_exchange(void *fc_opaq, const char *fc_input, int fc_inputlen,
						   char **fc_output, int *fc_outputlen,
						   const char **fc_logdetail);

/* 机制声明 */
const pg_be_sasl_mech pg_be_scram_mech = {
	fc_scram_get_mechanisms,
	fc_scram_init,
	fc_scram_exchange
};

/*
 * SCRAM 认证交换的状态数据。这应该保持
 * 在此文件内部。
 */
typedef enum
{
	SCRAM_AUTH_INIT,
	SCRAM_AUTH_SALT_SENT,
	SCRAM_AUTH_FINISHED
} scram_state_enum;

typedef struct
{
	scram_state_enum state;

	const char *username;		/* 启动包中的用户名 */

	Port	   *port;
	bool		channel_binding_in_use;

	int			iterations;
	char	   *salt;			/* base64 编码 */
	uint8		StoredKey[SCRAM_KEY_LEN];
	uint8		ServerKey[SCRAM_KEY_LEN];

	/* 客户端第一条消息的字段 */
	char		cbind_flag;
	char	   *client_first_message_bare;
	char	   *client_username;
	char	   *client_nonce;

	/* 客户端最后一条消息的字段 */
	char	   *client_final_message_without_proof;
	char	   *client_final_nonce;
	char		ClientProof[SCRAM_KEY_LEN];

	/* 服务器生成的字段 */
	char	   *server_first_message;
	char	   *server_nonce;

	/*
	 * 如果在认证过程中出现问题，或者我们正在进行
	 * “虚假”认证（见文件顶部的注释），将设置 ‘doomed’
	 * 标志。失败的原因将记录在
	 * 'logdetail' 中，以供服务器日志使用。
	 */
	bool		doomed;
	char	   *logdetail;
} scram_state;

static void fc_read_client_first_message(scram_state *fc_state, const char *fc_input);
static void fc_read_client_final_message(scram_state *fc_state, const char *fc_input);
static char *fc_build_server_first_message(scram_state *fc_state);
static char *fc_build_server_final_message(scram_state *fc_state);
static bool fc_verify_client_proof(scram_state *fc_state);
static bool fc_verify_final_nonce(scram_state *fc_state);
static void fc_mock_scram_secret(const char *fc_username, int *fc_iterations,
							  char **fc_salt, uint8 *fc_stored_key, uint8 *fc_server_key);
static bool fc_is_scram_printable(char *fc_p);
static char *fc_sanitize_char(char fc_c);
static char *fc_sanitize_str(const char *fc_s);
static char *fc_scram_mock_salt(const char *fc_username);

/*
 * 获取此模块支持的 SASL 机制列表。
 *
 * 为了方便构建列出
 * 机制的 FE/BE 数据包，名称附加到给定的 StringInfo 缓冲区，
 * 通过 '\0' 字节分隔。
 */
static void fc_scram_get_mechanisms(Port *fc_port, StringInfo fc_buf)
{
	/*
	 * 按重要性递减的顺序宣传机制。因此，通道绑定变体
	 * 优先考虑，如果它们被支持。通道绑定只在
	 * SSL 支持的情况下，以及 SSL 实现具有获取证书哈希
	 * 的功能时才被支持。
	 */
#ifdef HAVE_BE_TLS_GET_CERTIFICATE_HASH
	if (fc_port->ssl_in_use)
	{
		appendStringInfoString(fc_buf, SCRAM_SHA_256_PLUS_NAME);
		appendStringInfoChar(fc_buf, '\0');
	}
#endif
	appendStringInfoString(fc_buf, SCRAM_SHA_256_NAME);
	appendStringInfoChar(fc_buf, '\0');
}

/*
 * 初始化新的 SCRAM 认证交换状态跟踪器。在进行任何交换之前
 * 需要调用它。它将在交换开始后填充
 * 认证信息。
 *
 * 'selected_mech' 确定客户端选择的 SASL 机制。
 * 它应该是我们支持的机制之一，由
 * scram_get_mechanisms() 返回。
 *
 * 'shadow_pass' 是角色的存储秘密，来自 pg_authid.rolpassword。
 * 用户名由客户端在启动消息中提供，并且在 
 * port->user_name 中可用。如果 'shadow_pass' 为 NULL，
 * 我们仍然会进行认证交换，但它将失败，就像提供了错误的
 * 密码一样。
 */
static void * fc_scram_init(Port *fc_port, const char *fc_selected_mech, const char *fc_shadow_pass)
{
	scram_state *fc_state;
	bool		fc_got_secret;

	fc_state = (scram_state *) palloc0(sizeof(scram_state));
	fc_state->port = fc_port;
	fc_state->state = SCRAM_AUTH_INIT;

	/*
	 * 解析所选机制。
	 *
	 * 请注意，如果我们不支持通道绑定，无论是因为 SSL
	 * 实现不支持它，还是我们根本不使用 SSL，
	 * 我们都不会在第一时间宣传 PLUS 变体。如果客户端
	 * 仍然试图选择它，那就是协议违规，就像选择
	 * 我们不支持的任何其他 SASL 机制一样。
	 */
#ifdef HAVE_BE_TLS_GET_CERTIFICATE_HASH
	if (strcmp(fc_selected_mech, SCRAM_SHA_256_PLUS_NAME) == 0 && fc_port->ssl_in_use)
		fc_state->channel_binding_in_use = true;
	else
#endif
	if (strcmp(fc_selected_mech, SCRAM_SHA_256_NAME) == 0)
		fc_state->channel_binding_in_use = false;
	else
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("client selected an invalid SASL authentication mechanism")));

	/*
	 * 解析存储的秘密。
	 */
	if (fc_shadow_pass)
	{
		int			fc_password_type = get_password_type(fc_shadow_pass);

		if (fc_password_type == PASSWORD_TYPE_SCRAM_SHA_256)
		{
			if (parse_scram_secret(fc_shadow_pass, &fc_state->iterations, &fc_state->salt,
								   fc_state->StoredKey, fc_state->ServerKey))
				fc_got_secret = true;
			else
			{
				/*
				 * 密码看起来像一个 SCRAM 秘密，但无法被
				 * 解析。
				 */
				ereport(LOG,
						(errmsg("invalid SCRAM secret for user \"%s\"",
								fc_state->port->user_name)));
				fc_got_secret = false;
			}
		}
		else
		{
			/*
			 * 用户没有 SCRAM 秘密。（您无法使用 MD5 哈希
			 * 进行 SCRAM 认证。）
			 */
			fc_state->logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM secret."),
										fc_state->port->user_name);
			fc_got_secret = false;
		}
	}
	else
	{
		/*
		 * 调用者要求我们执行虚假认证。这被视为正常，因为
		 * 调用者请求了它，所以不要设置日志详细信息。
		 */
		fc_got_secret = false;
	}

	/*
	 * 如果用户没有有效的 SCRAM 秘密，我们仍然会通过
	 * 虚假的秘密进行操作，并失败，就像客户端提供了
	 * 错误的密码。这是为了避免向攻击者透露信息。
	 */
	if (!fc_got_secret)
	{
		fc_mock_scram_secret(fc_state->port->user_name, &fc_state->iterations,
						  &fc_state->salt, fc_state->StoredKey, fc_state->ServerKey);
		fc_state->doomed = true;
	}

	return fc_state;
}

/*
 * 继续进行 SCRAM 验证交换。
 *
 * 'input' 是客户端发送的 SCRAM 负载。在第一次调用时，
 * 'input' 包含客户端作为 SASLInitialResponse 消息一部分发送的
 * "初始客户端响应"，如果未提供初始客户端响应，则为 NULL。
 * （SASL 规范区分空响应和不存在的响应。）在后续调用中，'input'
 * 不能为空。为了方便本函数，调用者必须确保在 input[inputlen]
 * 处有一个 NULL 终止符。
 *
 * 要发送给客户端的下一个消息保存在 'output' 中，长度为 'outputlen'。
 * 如果出现错误，可选择将一个 palloc 分配的字符串存储在 *logdetail
 * 中，该字符串将发送到主控日志（但不会发送给客户端）。
 */
static int fc_scram_exchange(void *fc_opaq, const char *fc_input, int fc_inputlen,
			   char **fc_output, int *fc_outputlen, const char **fc_logdetail)
{
	scram_state *fc_state = (scram_state *) fc_opaq;
	int			fc_result;

	*fc_output = NULL;

	/*
	 * 如果客户端在 SASLInitialResponse 消息中未包含 "初始客户端响应"，
	 * 则发送一个空挑战，客户端将对此作出响应，回应与初始客户端响应
	 * 中通常包含的数据相同。
	 */
	if (fc_input == NULL)
	{
		Assert(fc_state->state == SCRAM_AUTH_INIT);

		*fc_output = pstrdup("");
		*fc_outputlen = 0;
		return PG_SASL_EXCHANGE_CONTINUE;
	}

	/*
	 * 检查输入长度是否与输入的字符串长度一致。
	 * 在这之后我们可以忽略 inputlen。
	 */
	if (fc_inputlen == 0)
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("malformed SCRAM message"),
				 errdetail("The message is empty.")));
	if (fc_inputlen != strlen(fc_input))
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("malformed SCRAM message"),
				 errdetail("Message length does not match input length.")));

	switch (fc_state->state)
	{
		case SCRAM_AUTH_INIT:

			/*
			 * 初始化阶段。接收来自客户端的第一个消息，并确保其被正确解析。
			 * 然后将挑战发送给客户端。
			 */
			fc_read_client_first_message(fc_state, fc_input);

			/* 准备发送挑战的消息 */
			*fc_output = fc_build_server_first_message(fc_state);

			fc_state->state = SCRAM_AUTH_SALT_SENT;
			fc_result = PG_SASL_EXCHANGE_CONTINUE;
			break;

		case SCRAM_AUTH_SALT_SENT:

			/*
			 * 服务器的最终阶段。接收先前发送的挑战的响应，验证并通知客户端
			 * 一切顺利（或不顺利）。
			 */
			fc_read_client_final_message(fc_state, fc_input);

			if (!fc_verify_final_nonce(fc_state))
				ereport(ERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("invalid SCRAM response"),
						 errdetail("Nonce does not match.")));

			/*
			 * 现在检查最终的 nonce 和客户端证明。
			 *
			 * 如果我们进行了“模拟”认证，事先知道会失败，
			 * 那么这个地方就是失败的地方。
			 *
			 * SCRAM 规范包括一个错误代码，
			 * "invalid-proof"，用于认证失败，但它也允许
			 * 以应用程序特定的方式出错。我们选择后者，
			 * 以便无效密码的错误信息对于所有认证方法相同。
			 * 调用者将在我们以没有输出返回 PG_SASL_EXCHANGE_FAILURE 时调用
			 * ereport()。
			 *
			 * 注意：这些检查的顺序是有意的。即使在模拟认证中，我们也会计算
			 * 客户端的证明，即使这注定会失败，以防范计时攻击来确定给定名称的角色
			 * 是否存在。
			 */
			if (!fc_verify_client_proof(fc_state) || fc_state->doomed)
			{
				fc_result = PG_SASL_EXCHANGE_FAILURE;
				break;
			}

			/* 为客户端构建最终消息 */
			*fc_output = fc_build_server_final_message(fc_state);

			/* 成功！ */
			fc_result = PG_SASL_EXCHANGE_SUCCESS;
			fc_state->state = SCRAM_AUTH_FINISHED;
			break;

		default:
			elog(ERROR, "invalid SCRAM exchange state");
			fc_result = PG_SASL_EXCHANGE_FAILURE;
	}

	if (fc_result == PG_SASL_EXCHANGE_FAILURE && fc_state->logdetail && fc_logdetail)
		*fc_logdetail = fc_state->logdetail;

	if (*fc_output)
		*fc_outputlen = strlen(*fc_output);

	return fc_result;
}

/*
 * 构建 SCRAM 密码，用于存储在 pg_authid.rolpassword 中。
 *
 * 结果是 palloc 分配的，因此调用者负责释放它。
 */
char * pg_be_scram_build_secret(const char *fc_password)
{
	char	   *fc_prep_password;
	pg_saslprep_rc fc_rc;
	char		fc_saltbuf[SCRAM_DEFAULT_SALT_LEN];
	char	   *fc_result;
	const char *fc_errstr = NULL;

	/*
	 * 使用 SASLprep 规范化密码。如果由于密码无效的 UTF-8 或包含禁用字符而失败，
	 * 则继续使用原始密码。（请参见文件顶部的注释。）
	 */
	fc_rc = pg_saslprep(fc_password, &fc_prep_password);
	if (fc_rc == SASLPREP_SUCCESS)
		fc_password = (const char *) fc_prep_password;

	/* 生成随机盐 */
	if (!pg_strong_random(fc_saltbuf, SCRAM_DEFAULT_SALT_LEN))
		ereport(ERROR,
				(errcode(ERRCODE_INTERNAL_ERROR),
				 errmsg("could not generate random salt")));

	fc_result = scram_build_secret(fc_saltbuf, SCRAM_DEFAULT_SALT_LEN,
								SCRAM_DEFAULT_ITERATIONS, fc_password,
								&fc_errstr);

	if (fc_prep_password)
		pfree(fc_prep_password);

	return fc_result;
}

/*
 * 验证明文密码与SCRAM密钥的匹配。这在对存储于pg_authid中的SCRAM密钥的用户进行明文密码认证时使用。
 */
bool scram_verify_plain_password(const char *fc_username, const char *fc_password,
							const char *fc_secret)
{
	char	   *fc_encoded_salt;
	char	   *fc_salt;
	int			fc_saltlen;
	int			fc_iterations;
	uint8		fc_salted_password[SCRAM_KEY_LEN];
	uint8		fc_stored_key[SCRAM_KEY_LEN];
	uint8		fc_server_key[SCRAM_KEY_LEN];
	uint8		fc_computed_key[SCRAM_KEY_LEN];
	char	   *fc_prep_password;
	pg_saslprep_rc fc_rc;
	const char *fc_errstr = NULL;

	if (!parse_scram_secret(fc_secret, &fc_iterations, &fc_encoded_salt,
							fc_stored_key, fc_server_key))
	{
		/*
		 * 密码看起来像SCRAM密钥，但无法解析。
		 */
		ereport(LOG,
				(errmsg("invalid SCRAM secret for user \"%s\"", fc_username)));
		return false;
	}

	fc_saltlen = pg_b64_dec_len(strlen(fc_encoded_salt));
	fc_salt = palloc(fc_saltlen);
	fc_saltlen = pg_b64_decode(fc_encoded_salt, strlen(fc_encoded_salt), fc_salt,
							fc_saltlen);
	if (fc_saltlen < 0)
	{
		ereport(LOG,
				(errmsg("invalid SCRAM secret for user \"%s\"", fc_username)));
		return false;
	}

	/* 规范化密码 */
	fc_rc = pg_saslprep(fc_password, &fc_prep_password);
	if (fc_rc == SASLPREP_SUCCESS)
		fc_password = fc_prep_password;

	/* 根据用户提供的明文密码计算服务器密钥 */
	if (scram_SaltedPassword(fc_password, fc_salt, fc_saltlen, fc_iterations,
							 fc_salted_password, &fc_errstr) < 0 ||
		scram_ServerKey(fc_salted_password, fc_computed_key, &fc_errstr) < 0)
	{
		elog(ERROR, "could not compute server key: %s", fc_errstr);
	}

	if (fc_prep_password)
		pfree(fc_prep_password);

	/*
	 * 将密钥的服务器密钥与从用户提供的密码计算的密钥进行比较。
	 */
	return memcmp(fc_computed_key, fc_server_key, SCRAM_KEY_LEN) == 0;
}


/*
 * 解析并验证给定SCRAM密钥的格式。
 *
 * 成功时，从密钥中提取迭代次数、盐值、存储的密钥和服务器密钥，并返回给调用者。对于' stored_key '和' server_key '，调用者必须传递预分配的SCRAM_KEY_LEN大小的缓冲区。盐值作为base64编码、以null结尾的字符串返回。盐值的缓冲区由此函数分配。
 *
 * 如果SCRAM密钥已被解析，则返回true，否则返回false。
 */
bool parse_scram_secret(const char *fc_secret, int *fc_iterations, char **fc_salt,
				   uint8 *fc_stored_key, uint8 *fc_server_key)
{
	char	   *fc_v;
	char	   *fc_p;
	char	   *fc_scheme_str;
	char	   *fc_salt_str;
	char	   *fc_iterations_str;
	char	   *fc_storedkey_str;
	char	   *fc_serverkey_str;
	int			fc_decoded_len;
	char	   *fc_decoded_salt_buf;
	char	   *fc_decoded_stored_buf;
	char	   *fc_decoded_server_buf;

	/*
	 * 密钥的形式为：
	 *
	 * SCRAM-SHA-256$<iterations>:<salt>$<storedkey>:<serverkey>
	 */
	fc_v = pstrdup(fc_secret);
	if ((fc_scheme_str = strtok(fc_v, "$")) == NULL)
		goto invalid_secret;
	if ((fc_iterations_str = strtok(NULL, ":")) == NULL)
		goto invalid_secret;
	if ((fc_salt_str = strtok(NULL, "$")) == NULL)
		goto invalid_secret;
	if ((fc_storedkey_str = strtok(NULL, ":")) == NULL)
		goto invalid_secret;
	if ((fc_serverkey_str = strtok(NULL, "")) == NULL)
		goto invalid_secret;

	/* 解析字段 */
	if (strcmp(fc_scheme_str, "SCRAM-SHA-256") != 0)
		goto invalid_secret;

	errno = 0;
	*fc_iterations = strtol(fc_iterations_str, &fc_p, 10);
	if (*fc_p || errno != 0)
		goto invalid_secret;

	/*
	 * 通过解码，验证盐值是否为Base64编码格式，尽管我们将编码版本返回给调用者。
	 */
	fc_decoded_len = pg_b64_dec_len(strlen(fc_salt_str));
	fc_decoded_salt_buf = palloc(fc_decoded_len);
	fc_decoded_len = pg_b64_decode(fc_salt_str, strlen(fc_salt_str),
								fc_decoded_salt_buf, fc_decoded_len);
	if (fc_decoded_len < 0)
		goto invalid_secret;
	*fc_salt = pstrdup(fc_salt_str);

	/*
	 * 解码StoredKey和ServerKey。
	 */
	fc_decoded_len = pg_b64_dec_len(strlen(fc_storedkey_str));
	fc_decoded_stored_buf = palloc(fc_decoded_len);
	fc_decoded_len = pg_b64_decode(fc_storedkey_str, strlen(fc_storedkey_str),
								fc_decoded_stored_buf, fc_decoded_len);
	if (fc_decoded_len != SCRAM_KEY_LEN)
		goto invalid_secret;
	memcpy(fc_stored_key, fc_decoded_stored_buf, SCRAM_KEY_LEN);

	fc_decoded_len = pg_b64_dec_len(strlen(fc_serverkey_str));
	fc_decoded_server_buf = palloc(fc_decoded_len);
	fc_decoded_len = pg_b64_decode(fc_serverkey_str, strlen(fc_serverkey_str),
								fc_decoded_server_buf, fc_decoded_len);
	if (fc_decoded_len != SCRAM_KEY_LEN)
		goto invalid_secret;
	memcpy(fc_server_key, fc_decoded_server_buf, SCRAM_KEY_LEN);

	return true;

invalid_secret:
	*fc_salt = NULL;
	return false;
}

/*
 * 生成合理的SCRAM密钥参数以用于模拟认证。
 *
 * 在正常认证中，这些值是从存储在服务器中的密钥提取的。此函数生成看起来真实的值，用于没有存储密钥的情况。
 *
 * 与parse_scram_secret()中的一样，对于' stored_key '和' server_key '，调用者必须传递预分配的SCRAM_KEY_LEN大小的缓冲区，盐值的缓冲区由此函数分配。
 */
static void fc_mock_scram_secret(const char *fc_username, int *fc_iterations, char **fc_salt,
				  uint8 *fc_stored_key, uint8 *fc_server_key)
{
	char	   *fc_raw_salt;
	char	   *fc_encoded_salt;
	int			fc_encoded_len;

	/*
	 * 生成确定性的盐值。
	 *
	 * 请注意，我们无法向攻击者透露任何信息，因此错误信息需要保持通用。这应该无论如何都不会失败，因为为模拟认证生成的盐值使用的是集群的nonce值。
	 */
	fc_raw_salt = fc_scram_mock_salt(fc_username);
	if (fc_raw_salt == NULL)
		elog(ERROR, "could not encode salt");

	fc_encoded_len = pg_b64_enc_len(SCRAM_DEFAULT_SALT_LEN);
	/* 别忘了零终止符 */
	fc_encoded_salt = (char *) palloc(fc_encoded_len + 1);
	fc_encoded_len = pg_b64_encode(fc_raw_salt, SCRAM_DEFAULT_SALT_LEN, fc_encoded_salt,
								fc_encoded_len);

	if (fc_encoded_len < 0)
		elog(ERROR, "could not encode salt");
	fc_encoded_salt[fc_encoded_len] = '\0';

	*fc_salt = fc_encoded_salt;
	*fc_iterations = SCRAM_DEFAULT_ITERATIONS;

	/* StoredKey和ServerKey在注定失败的认证中未被使用 */
	memset(fc_stored_key, 0, SCRAM_KEY_LEN);
	memset(fc_server_key, 0, SCRAM_KEY_LEN);
}

/*
 * 读取给定SCRAM交换消息中给定属性的值。
 */
static char * fc_read_attr_value(char **fc_input, char fc_attr)
{
	char	   *fc_begin = *fc_input;
	char	   *fc_end;

	if (*fc_begin != fc_attr)
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("malformed SCRAM message"),
				 errdetail("Expected attribute \"%c\" but found \"%s\".",
						   fc_attr, fc_sanitize_char(*fc_begin))));
	fc_begin++;

	if (*fc_begin != '=')
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("malformed SCRAM message"),
				 errdetail("Expected character \"=\" for attribute \"%c\".", fc_attr)));
	fc_begin++;

	fc_end = fc_begin;
	while (*fc_end && *fc_end != ',')
		fc_end++;

	if (*fc_end)
	{
		*fc_end = '\0';
		*fc_input = fc_end + 1;
	}
	else
		*fc_input = fc_end;

	return fc_begin;
}

static bool fc_is_scram_printable(char *fc_p)
{
	/*------
	 * 可打印字符，由SCRAM规范定义：(RFC 5802)
	 *
	 *	printable		= %x21-2B / %x2D-7E
	 *					  ;; 可打印ASCII，除了“,”。
	 *					  ;; 注意，任何“可打印”字符也是
	 *					  ;; 一个有效的“值”。
	 *------
	 */
	for (; *fc_p; fc_p++)
	{
		if (*fc_p < 0x21 || *fc_p > 0x7E || *fc_p == 0x2C /* 逗号 */ )
			return false;
	}
	return true;
}

/*
 * 将任意字节转换为可打印形式。用于错误消息。
 *
 * 如果它是一个可打印的ASCII字符，则作为单个字符打印。
 * 否则，在十六进制中打印。
 *
 * 返回的指针指向一个静态缓冲区。
 */
static char * fc_sanitize_char(char fc_c)
{
	static char fc_buf[5];

	if (fc_c >= 0x21 && fc_c <= 0x7E)
		snprintf(fc_buf, sizeof(fc_buf), "'%c'", fc_c);
	else
		snprintf(fc_buf, sizeof(fc_buf), "0x%02x", (unsigned char) fc_c);
	return fc_buf;
}

/*
 * 将任意字符串转换为可打印形式，用于错误消息。
 *
 * 任何不是可打印 ASCII 字符的内容都将被替换为
 * '?'，并且字符串将被截断为 30 个字符。
 *
 * 返回的指针指向一个静态缓冲区。
 */
static char * fc_sanitize_str(const char *fc_s)
{
	static char fc_buf[30 + 1];
	int			fc_i;

	for (fc_i = 0; fc_i < sizeof(fc_buf) - 1; fc_i++)
	{
		char		fc_c = fc_s[fc_i];

		if (fc_c == '\0')
			break;

		if (fc_c >= 0x21 && fc_c <= 0x7E)
			fc_buf[fc_i] = fc_c;
		else
			fc_buf[fc_i] = '?';
	}
	fc_buf[fc_i] = '\0';
	return fc_buf;
}

/*
 * 读取 SCRAM 交换消息中的下一个属性和值。
 *
 * 属性字符设置在 *attr_p 中，属性值是返回值。
 */
static char * fc_read_any_attr(char **fc_input, char *fc_attr_p)
{
	char	   *fc_begin = *fc_input;
	char	   *fc_end;
	char		fc_attr = *fc_begin;

	if (fc_attr == '\0')
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("malformed SCRAM message"),
				 errdetail("Attribute expected, but found end of string.")));

	/*------
	 * attr-val		   = ALPHA "=" value
	 *					 ;; 由服务器或客户端发送的任何属性的通用语法
	 *------
	 */
	if (!((fc_attr >= 'A' && fc_attr <= 'Z') ||
		  (fc_attr >= 'a' && fc_attr <= 'z')))
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("malformed SCRAM message"),
				 errdetail("Attribute expected, but found invalid character \"%s\".",
						   fc_sanitize_char(fc_attr))));
	if (fc_attr_p)
		*fc_attr_p = fc_attr;
	fc_begin++;

	if (*fc_begin != '=')
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("malformed SCRAM message"),
				 errdetail("Expected character \"=\" for attribute \"%c\".", fc_attr)));
	fc_begin++;

	fc_end = fc_begin;
	while (*fc_end && *fc_end != ',')
		fc_end++;

	if (*fc_end)
	{
		*fc_end = '\0';
		*fc_input = fc_end + 1;
	}
	else
		*fc_input = fc_end;

	return fc_begin;
}

/*
 * 在 SCRAM 身份验证交换消息的上下文中读取并解析来自客户端的首个消息。
 *
 * 在此阶段，任何错误都将直接通过 ereport(ERROR) 报告。
 */
static void fc_read_client_first_message(scram_state *fc_state, const char *fc_input)
{
	char	   *fc_p = pstrdup(fc_input);
	char	   *fc_channel_binding_type;


	/*------
	 * 客户端首个消息的语法是: (RFC 5802)
	 *
	 * saslname		   = 1*(value-safe-char / "=2C" / "=3D")
	 *					 ;; 符合 <value>。
	 *
	 * authzid		   = "a=" saslname
	 *					 ;; 协议特定的。
	 *
	 * cb-name		   = 1*(ALPHA / DIGIT / "." / "-")
	 *					  ;; 参见 RFC 5056，第 7 节。
	 *					  ;; 例如，“tls-server-end-point”或
	 *					  ;; “tls-unique”。
	 *
	 * gs2-cbind-flag  = ("p=" cb-name) / "n" / "y"
	 *					 ;; "n" -> 客户端不支持通道绑定。
	 *					 ;; "y" -> 客户端支持通道绑定
	 *					 ;;		   但认为服务器不支持。
	 *					 ;; "p" -> 客户端要求通道绑定。
	 *					 ;; 选择的通道绑定跟随 "p="。
	 *
	 * gs2-header	   = gs2-cbind-flag "," [ authzid ] ","
	 *					 ;; SCRAM 的 GS2 头部
	 *					 ;; (实际的 GS2 头部包含一个可选
	 *					 ;; 标志，以指示 GSS 机制不是
	 *					 ;; "标准的"，但由于 SCRAM 是 "标准的"，我们
	 *					 ;; 不包括该标志)。
	 *
	 * username		   = "n=" saslname
	 *					 ;; 用户名通过 SASLprep 准备。
	 *
	 * reserved-mext  = "m=" 1*(value-char)
	 *					 ;; 保留用于信号强制扩展。
	 *					 ;; 确切的语法将在
	 *					 ;; 未来定义。
	 *
	 * nonce		   = "r=" c-nonce [s-nonce]
	 *					 ;; 由服务器提供的第二部分。
	 *
	 * c-nonce		   = 可打印
	 *
	 * client-first-message-bare =
	 *					 [reserved-mext ","]
	 *					 username "," nonce ["," extensions]
	 *
	 * client-first-message =
	 *					 gs2-header client-first-message-bare
	 *
	 * 例如：
	 * n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL
	 *
	 * 开头的 "n,," 表示客户端不支持
	 * 通道绑定，且没有给出 authzid。"n=user" 是用户名。
	 * 然而，在 PostgreSQL 中，用户名在启动数据包中发送，
	 * SCRAM 交换中的用户名被忽略。libpq 始终将其作为
	 * 空字符串发送。最后一部分 "r=fyko+d2lbbFgONRv9qkxdawL" 是
	 * 客户端随机数。
	 *------
	 */

	/*
	 * 读取 gs2-cbind-flag。 （有关详细信息，请参见 RFC 5802 第 6 节“通道绑定”。）
	 */
	fc_state->cbind_flag = *fc_p;
	switch (*fc_p)
	{
		case 'n':

			/*
			 * 客户端不支持通道绑定或者简单地决定不使用它。 在这种情况下，直接忽略它。
			 */
			if (fc_state->channel_binding_in_use)
				ereport(ERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("malformed SCRAM message"),
						 errdetail("The client selected SCRAM-SHA-256-PLUS, but the SCRAM message does not include channel binding data.")));

			fc_p++;
			if (*fc_p != ',')
				ereport(ERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("malformed SCRAM message"),
						 errdetail("Comma expected, but found character \"%s\".",
								   fc_sanitize_char(*fc_p))));
			fc_p++;
			break;
		case 'y':

			/*
			 * 客户端支持通道绑定，并认为服务器不支持。 在这种情况下，服务器必须在支持通道绑定的情况下失败身份验证。
			 */
			if (fc_state->channel_binding_in_use)
				ereport(ERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("malformed SCRAM message"),
						 errdetail("The client selected SCRAM-SHA-256-PLUS, but the SCRAM message does not include channel binding data.")));

#ifdef HAVE_BE_TLS_GET_CERTIFICATE_HASH
			if (fc_state->port->ssl_in_use)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
						 errmsg("SCRAM channel binding negotiation error"),
						 errdetail("The client supports SCRAM channel binding but thinks the server does not.  "
								   "However, this server does support channel binding.")));
#endif
			fc_p++;
			if (*fc_p != ',')
				ereport(ERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("malformed SCRAM message"),
						 errdetail("Comma expected, but found character \"%s\".",
								   fc_sanitize_char(*fc_p))));
			fc_p++;
			break;
		case 'p':

			/*
			 * 客户端要求通道绑定。 通道绑定类型如下，例如，“p=tls-server-end-point”。
			 */
			if (!fc_state->channel_binding_in_use)
				ereport(ERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("malformed SCRAM message"),
						 errdetail("The client selected SCRAM-SHA-256 without channel binding, but the SCRAM message includes channel binding data.")));

			fc_channel_binding_type = fc_read_attr_value(&fc_p, 'p');

			/*
			 * 我们支持的唯一通道绑定类型是
			 * tls-server-end-point。
			 */
			if (strcmp(fc_channel_binding_type, "tls-server-end-point") != 0)
				ereport(ERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("unsupported SCRAM channel-binding type \"%s\"",
								fc_sanitize_str(fc_channel_binding_type))));
			break;
		default:
			ereport(ERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("malformed SCRAM message"),
					 errdetail("Unexpected channel-binding flag \"%s\".",
							   fc_sanitize_char(*fc_p))));
	}

	/*
	 * 禁止可选的 authzid（授权身份）。 我们不支持它。
	 */
	if (*fc_p == 'a')
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("client uses authorization identity, but it is not supported")));
	if (*fc_p != ',')
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("malformed SCRAM message"),
				 errdetail("Unexpected attribute \"%s\" in client-first-message.",
						   fc_sanitize_char(*fc_p))));
	fc_p++;

	fc_state->client_first_message_bare = pstrdup(fc_p);

	/*
	 * 任何强制性扩展将在此处列出。 我们不支持任何扩展。
	 *
	 * RFC 5802 为此指定错误代码“e=extensions-not-supported”，
	 * 但只能在 server-final 消息中发送。 我们更倾向于立即失败
	 * （RFC 也允许这样）。
	 */
	if (*fc_p == 'm')
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("client requires an unsupported SCRAM extension")));

	/*
	 * 读取用户名。 注意：这被忽略。 我们使用启动消息中的用户名，
	 * 但是如果提供它，仍然会保留，因为它证明对调试 purposes 有用。
	 */
	fc_state->client_username = fc_read_attr_value(&fc_p, 'n');

	/* 读取随机数并检查它是否仅由可打印字符构成 */
	fc_state->client_nonce = fc_read_attr_value(&fc_p, 'r');
	if (!fc_is_scram_printable(fc_state->client_nonce))
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("non-printable characters in SCRAM nonce")));

	/*
	 * 在此之后可以有任意数量的可选扩展。 我们不支持任何扩展，因此忽略它们。
	 */
	while (*fc_p != '\0')
		fc_read_any_attr(&fc_p, NULL);

	/* 成功！ */
}

/*
 * 验证在交换中从客户端接收到的最后一条消息中包含的最终随机数。
 */
static bool fc_verify_final_nonce(scram_state *fc_state)
{
	int			fc_client_nonce_len = strlen(fc_state->client_nonce);
	int			fc_server_nonce_len = strlen(fc_state->server_nonce);
	int			fc_final_nonce_len = strlen(fc_state->client_final_nonce);

	if (fc_final_nonce_len != fc_client_nonce_len + fc_server_nonce_len)
		return false;
	if (memcmp(fc_state->client_final_nonce, fc_state->client_nonce, fc_client_nonce_len) != 0)
		return false;
	if (memcmp(fc_state->client_final_nonce + fc_client_nonce_len, fc_state->server_nonce, fc_server_nonce_len) != 0)
		return false;

	return true;
}

/*
 * 验证在交换中从客户端接收到的最后一条消息中包含的客户端证明。 如果验证成功，则返回 true，
 * 否则返回 false。
 */
static bool fc_verify_client_proof(scram_state *fc_state)
{
	uint8		fc_ClientSignature[SCRAM_KEY_LEN];
	uint8		fc_ClientKey[SCRAM_KEY_LEN];
	uint8		fc_client_StoredKey[SCRAM_KEY_LEN];
	pg_hmac_ctx *fc_ctx = pg_hmac_create(PG_SHA256);
	int			fc_i;
	const char *fc_errstr = NULL;

	/*
	 * 计算 ClientSignature。 注意，我们在此处即使在处理计算时也不直接记录失败，
	 * 因为这可能涉及模拟身份验证。
	 */
	if (pg_hmac_init(fc_ctx, fc_state->StoredKey, SCRAM_KEY_LEN) < 0 ||
		pg_hmac_update(fc_ctx,
					   (uint8 *) fc_state->client_first_message_bare,
					   strlen(fc_state->client_first_message_bare)) < 0 ||
		pg_hmac_update(fc_ctx, (uint8 *) ",", 1) < 0 ||
		pg_hmac_update(fc_ctx,
					   (uint8 *) fc_state->server_first_message,
					   strlen(fc_state->server_first_message)) < 0 ||
		pg_hmac_update(fc_ctx, (uint8 *) ",", 1) < 0 ||
		pg_hmac_update(fc_ctx,
					   (uint8 *) fc_state->client_final_message_without_proof,
					   strlen(fc_state->client_final_message_without_proof)) < 0 ||
		pg_hmac_final(fc_ctx, fc_ClientSignature, sizeof(fc_ClientSignature)) < 0)
	{
		elog(ERROR, "could not calculate client signature: %s",
			 pg_hmac_error(fc_ctx));
	}

	pg_hmac_free(fc_ctx);

	/* 提取客户端从证明中计算的 ClientKey */
	for (fc_i = 0; fc_i < SCRAM_KEY_LEN; fc_i++)
		fc_ClientKey[fc_i] = fc_state->ClientProof[fc_i] ^ fc_ClientSignature[fc_i];

	/* 再次哈希，并与 StoredKey 进行比较 */
	if (scram_H(fc_ClientKey, SCRAM_KEY_LEN, fc_client_StoredKey, &fc_errstr) < 0)
		elog(ERROR, "could not hash stored key: %s", fc_errstr);

	if (memcmp(fc_client_StoredKey, fc_state->StoredKey, SCRAM_KEY_LEN) != 0)
		return false;

	return true;
}

/*
 * 构建发送给客户端的第一个服务器端消息，作为 SCRAM
 * 通信交换中的一部分。
 */
static char * fc_build_server_first_message(scram_state *fc_state)
{
	/*------
	 * 服务器首条消息的语法为：（RFC 5802）
	 *
	 * server-first-message =
	 *					 [reserved-mext "," ] nonce "," salt ","
	 *					 iteration-count [ "," extensions ]
	 *
	 * nonce		   = "r=" c-nonce [s-nonce]
	 *					 ;; 第二部分由服务器提供。
	 *
	 * c-nonce		   = 可打印字符
	 *
	 * s-nonce		   = 可打印字符
	 *
	 * salt			   = "s=" base64
	 *
	 * iteration-count = "i=" 正数
	 *					 ;; 正数。
	 *
	 * 例子：
	 *
	 * r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096
	 *------
	 */

	/*
	 * 根据规范，随机数可以由任何可打印的 ASCII 字符组成。
	 * 然而，为了方便，我们并不使用可用的整个范围，
	 * 而是生成一些随机字节，并对其进行 base64 编码。
	 */
	char		fc_raw_nonce[SCRAM_RAW_NONCE_LEN];
	int			fc_encoded_len;

	if (!pg_strong_random(fc_raw_nonce, SCRAM_RAW_NONCE_LEN))
		ereport(ERROR,
				(errcode(ERRCODE_INTERNAL_ERROR),
				 errmsg("could not generate random nonce")));

	fc_encoded_len = pg_b64_enc_len(SCRAM_RAW_NONCE_LEN);
	/* 别忘了零终止符 */
	fc_state->server_nonce = palloc(fc_encoded_len + 1);
	fc_encoded_len = pg_b64_encode(fc_raw_nonce, SCRAM_RAW_NONCE_LEN,
								fc_state->server_nonce, fc_encoded_len);
	if (fc_encoded_len < 0)
		ereport(ERROR,
				(errcode(ERRCODE_INTERNAL_ERROR),
				 errmsg("could not encode random nonce")));
	fc_state->server_nonce[fc_encoded_len] = '\0';

	fc_state->server_first_message =
		psprintf("r=%s%s,s=%s,i=%d",
				 fc_state->client_nonce, fc_state->server_nonce,
				 fc_state->salt, fc_state->iterations);

	return pstrdup(fc_state->server_first_message);
}


/*
 * 读取并解析从客户端接收到的最后一条消息。
 */
static void fc_read_client_final_message(scram_state *fc_state, const char *fc_input)
{
	char		fc_attr;
	char	   *fc_channel_binding;
	char	   *fc_value;
	char	   *fc_begin,
			   *fc_proof;
	char	   *fc_p;
	char	   *fc_client_proof;
	int			fc_client_proof_len;

	fc_begin = fc_p = pstrdup(fc_input);

	/*------
	 * 服务器首次消息的语法为： (RFC 5802)
	 *
	 * gs2-header	   = gs2-cbind-flag "," [ authzid ] ","
	 *					 ;; SCRAM 的 GS2 头
	 *					 ;; (实际的 GS2 头包括一个可选的
	 *					 ;; 标志，以指示 GSS 机制不是
	 *					 ;; “标准”的，但由于 SCRAM 是“标准”，我们
	 *					 ;; 不包括该标志)。
	 *
	 * cbind-input	 = gs2-header [ cbind-data ]
	 *					 ;; 对于
	 *					 ;; gs2-cbind-flag 为 "p"，cbind-data 必须存在
	 *					 ;; 对于 "y" 或 "n"，cbind-data 必须不存在。
	 *
	 * channel-binding = "c=" base64
	 *					 ;; cbind-input 的 base64 编码。
	 *
	 * proof		   = "p=" base64
	 *
	 * client-final-message-without-proof =
	 *					 channel-binding "," nonce [","
	 *					 extensions]
	 *
	 * client-final-message =
	 *					 client-final-message-without-proof "," proof
	 *------
	 */

	/*
	 * 读取通道绑定。 这重复了通道绑定标志，然后根据类型
	 * 后跟实际绑定数据。
	 */
	fc_channel_binding = fc_read_attr_value(&fc_p, 'c');
	if (fc_state->channel_binding_in_use)
	{
#ifdef HAVE_BE_TLS_GET_CERTIFICATE_HASH
		const char *fc_cbind_data = NULL;
		size_t		fc_cbind_data_len = 0;
		size_t		fc_cbind_header_len;
		char	   *fc_cbind_input;
		size_t		fc_cbind_input_len;
		char	   *fc_b64_message;
		int			fc_b64_message_len;

		Assert(fc_state->cbind_flag == 'p');

		/* 获取服务器的 SSL 证书的哈希数据 */
		fc_cbind_data = be_tls_get_certificate_hash(fc_state->port,
												 &fc_cbind_data_len);

		/* 不应该发生 */
		if (fc_cbind_data == NULL || fc_cbind_data_len == 0)
			elog(ERROR, "could not get server certificate hash");

		fc_cbind_header_len = strlen("p=tls-server-end-point,,");	/* p=类型,, */
		fc_cbind_input_len = fc_cbind_header_len + fc_cbind_data_len;
		fc_cbind_input = palloc(fc_cbind_input_len);
		snprintf(fc_cbind_input, fc_cbind_input_len, "p=tls-server-end-point,,");
		memcpy(fc_cbind_input + fc_cbind_header_len, fc_cbind_data, fc_cbind_data_len);

		fc_b64_message_len = pg_b64_enc_len(fc_cbind_input_len);
		/* 别忘了零终止符 */
		fc_b64_message = palloc(fc_b64_message_len + 1);
		fc_b64_message_len = pg_b64_encode(fc_cbind_input, fc_cbind_input_len,
										fc_b64_message, fc_b64_message_len);
		if (fc_b64_message_len < 0)
			elog(ERROR, "could not encode channel binding data");
		fc_b64_message[fc_b64_message_len] = '\0';

		/*
		 * 将客户端发送的值与服务器预期的值进行比较。
		 */
		if (strcmp(fc_channel_binding, fc_b64_message) != 0)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION),
					 errmsg("SCRAM channel binding check failed")));
#else
		/* 不应该发生，因为我们之前已经检查过了 */
		elog(ERROR, "channel binding not supported by this build");
#endif
	}
	else
	{
		/*
		 * 如果我们不使用通道绑定，则期望绑定数据始终为 "biws"，
		 * 其为 "n,," 的 base64 编码，或 "eSws"，其为 "y,,"。
		 * 我们还必须检查标志是否与客户端最初发送的相同。
		 */
		if (!(strcmp(fc_channel_binding, "biws") == 0 && fc_state->cbind_flag == 'n') &&
			!(strcmp(fc_channel_binding, "eSws") == 0 && fc_state->cbind_flag == 'y'))
			ereport(ERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("unexpected SCRAM channel-binding attribute in client-final-message")));
	}

	fc_state->client_final_nonce = fc_read_attr_value(&fc_p, 'r');

	/* 忽略可选扩展，读取直到找到 "p" 属性 */
	do
	{
		fc_proof = fc_p - 1;
		fc_value = fc_read_any_attr(&fc_p, &fc_attr);
	} while (fc_attr != 'p');

	fc_client_proof_len = pg_b64_dec_len(strlen(fc_value));
	fc_client_proof = palloc(fc_client_proof_len);
	if (pg_b64_decode(fc_value, strlen(fc_value), fc_client_proof,
					  fc_client_proof_len) != SCRAM_KEY_LEN)
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("malformed SCRAM message"),
				 errdetail("Malformed proof in client-final-message.")));
	memcpy(fc_state->ClientProof, fc_client_proof, SCRAM_KEY_LEN);
	pfree(fc_client_proof);

	if (*fc_p != '\0')
		ereport(ERROR,
				(errcode(ERRCODE_PROTOCOL_VIOLATION),
				 errmsg("malformed SCRAM message"),
				 errdetail("Garbage found at the end of client-final-message.")));

	fc_state->client_final_message_without_proof = palloc(fc_proof - fc_begin + 1);
	memcpy(fc_state->client_final_message_without_proof, fc_input, fc_proof - fc_begin);
	fc_state->client_final_message_without_proof[fc_proof - fc_begin] = '\0';
}

/*
 * 构建交换的最终服务器端消息。
 */
static char * fc_build_server_final_message(scram_state *fc_state)
{
	uint8		fc_ServerSignature[SCRAM_KEY_LEN];
	char	   *fc_server_signature_base64;
	int			fc_siglen;
	pg_hmac_ctx *fc_ctx = pg_hmac_create(PG_SHA256);

	/* 计算 ServerSignature */
	if (pg_hmac_init(fc_ctx, fc_state->ServerKey, SCRAM_KEY_LEN) < 0 ||
		pg_hmac_update(fc_ctx,
					   (uint8 *) fc_state->client_first_message_bare,
					   strlen(fc_state->client_first_message_bare)) < 0 ||
		pg_hmac_update(fc_ctx, (uint8 *) ",", 1) < 0 ||
		pg_hmac_update(fc_ctx,
					   (uint8 *) fc_state->server_first_message,
					   strlen(fc_state->server_first_message)) < 0 ||
		pg_hmac_update(fc_ctx, (uint8 *) ",", 1) < 0 ||
		pg_hmac_update(fc_ctx,
					   (uint8 *) fc_state->client_final_message_without_proof,
					   strlen(fc_state->client_final_message_without_proof)) < 0 ||
		pg_hmac_final(fc_ctx, fc_ServerSignature, sizeof(fc_ServerSignature)) < 0)
	{
		elog(ERROR, "could not calculate server signature: %s",
			 pg_hmac_error(fc_ctx));
	}

	pg_hmac_free(fc_ctx);

	fc_siglen = pg_b64_enc_len(SCRAM_KEY_LEN);
	/* 别忘了零终止符 */
	fc_server_signature_base64 = palloc(fc_siglen + 1);
	fc_siglen = pg_b64_encode((const char *) fc_ServerSignature,
						   SCRAM_KEY_LEN, fc_server_signature_base64,
						   fc_siglen);
	if (fc_siglen < 0)
		elog(ERROR, "could not encode server signature");
	fc_server_signature_base64[fc_siglen] = '\0';

	/*------
	 * 服务器最终消息的语法为： (RFC 5802)
	 *
	 * verifier		   = "v=" base64
	 *					 ;; base64 编码的 ServerSignature。
	 *
	 * server-final-message = (server-error / verifier)
	 *					 ["," extensions]
	 *
	 *------
	 */
	return psprintf("v=%s", fc_server_signature_base64);
}


/*
 * 确定性生成用于模拟身份验证的盐值，使用基于用户名和集群级别
 * 的秘密密钥的 SHA256 哈希。 返回指向大小为SCRAM_DEFAULT_SALT_LEN的静态缓冲区的指针，或 NULL。
 */
static char * fc_scram_mock_salt(const char *fc_username)
{
	pg_cryptohash_ctx *fc_ctx;
	static uint8 fc_sha_digest[PG_SHA256_DIGEST_LENGTH];
	char	   *fc_mock_auth_nonce = GetMockAuthenticationNonce();

	/*
	 * 使用用户名和集群的模拟身份验证随机数生成盐值的 SHA256 哈希。
	 * (只要盐值长度不大于 SHA256 摘要长度，这就有效。
	 * 如果盐值较小，调用者将忽略多余的数据。)
	 */
	StaticAssertStmt(PG_SHA256_DIGEST_LENGTH >= SCRAM_DEFAULT_SALT_LEN,
					 "salt length greater than SHA256 digest length");

	fc_ctx = pg_cryptohash_create(PG_SHA256);
	if (pg_cryptohash_init(fc_ctx) < 0 ||
		pg_cryptohash_update(fc_ctx, (uint8 *) fc_username, strlen(fc_username)) < 0 ||
		pg_cryptohash_update(fc_ctx, (uint8 *) fc_mock_auth_nonce, MOCK_AUTH_NONCE_LEN) < 0 ||
		pg_cryptohash_final(fc_ctx, fc_sha_digest, sizeof(fc_sha_digest)) < 0)
	{
		pg_cryptohash_free(fc_ctx);
		return NULL;
	}
	pg_cryptohash_free(fc_ctx);

	return (char *) fc_sha_digest;
}
